// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "planetaryatmosphererenderable.h"
#include "planetaryatmosphere.h"
#include "planetaryplanet.h"
#include "planetaryoptions.h"
#include "planetaryscenemanager.h"
#include "planetarycommon.h"
#include "planetaryheightmapdata.h"
#include "planetaryindexbuffermanager.h"
#include "planetaryzone.h"
#include <Ogre.h>

namespace Ogre
{
	String PlanetaryAtmosphereRenderable::mType = "PlanetaryAtmosphereRenderable";

	PlanetaryAtmosphereRenderable::PlanetaryAtmosphereRenderable(PlanetarySceneManager *mgr) : Renderable(), MovableObject()
	{
		mSceneMgr = mgr;
		mOptions = mgr->getOptions();

		// TODO constant because of limitations in shader
		mRadius = mOptions->mPlanetRadius * 1.025;

		mVertices[LOD_MAX] = 0;
		mVertices[LOD_MID] = 0;
		mVertices[LOD_MIN] = 0;
		mIndices[LOD_MAX] = 0;
		mIndices[LOD_MID] = 0;
		mIndices[LOD_MIN] = 0;
		// 0->2, 0 being the best
		mLodIndex = 2;

		// FIXME: Read this from init aswell
		mPlanet = 0;

		mLightListDirty = true;
		mIsLoaded = false;
		mIsInit = false;
	}

	PlanetaryAtmosphereRenderable::~PlanetaryAtmosphereRenderable()
	{
		if(mIsLoaded)
			unload();

		if(mIsInit)
			uninit();
	}
	
	void PlanetaryAtmosphereRenderable::init(unsigned int zone, Ogre::PlanetaryAtmosphere *parent)
	{
		assert(!mIsInit);

		mZone = zone;
		mParent = parent;

		mCastShadows = false;

		const unsigned int rowlength0 = mOptions->mAtmoSizeLOD0;
		const unsigned int rowlength1 = mOptions->mAtmoSizeLOD1;
		const unsigned int rowlength2 = mOptions->mAtmoSizeLOD2;

		mMaterial = MaterialManager::getSingleton().getByName(mOptions->mPlanetMaterial + "_skyFromSpace");

		// Default query flags to top bit so users can exclude it if they wish
		MovableObject::mQueryFlags = SceneManager::WORLD_GEOMETRY_TYPE_MASK;
		MovableObject::setRenderQueueGroup(mSceneMgr->getWorldGeometryRenderQueue());

		// LOD MAX
		mVertices[0] = new VertexData();
		mVertices[0]->vertexStart = 0;
		mVertices[0]->vertexCount = (rowlength0 * rowlength0);

		VertexDeclaration *decl = mVertices[0]->vertexDeclaration;

		size_t offset = 0;
		decl->addElement(0,0,VET_FLOAT3,VES_POSITION);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT3, VES_NORMAL);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT2, VES_TEXTURE_COORDINATES);
		offset += VertexElement::getTypeSize(VET_FLOAT2);

		mIndices[0] = new IndexData();
		mIndices[0]->indexStart = 0;
		mIndices[0]->indexCount = mSceneMgr->getIndexBufferManager()->getIndexBufferSize(INDEX_BUFFER_ATMO,LOD_MAX);

		// LOD MID
		mVertices[1] = new VertexData();
		mVertices[1]->vertexStart = 0;
		mVertices[1]->vertexCount = (rowlength1 * rowlength1);

		decl = mVertices[1]->vertexDeclaration;

		offset = 0;
		decl->addElement(0,0,VET_FLOAT3,VES_POSITION);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT3, VES_NORMAL);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT2, VES_TEXTURE_COORDINATES);
		offset += VertexElement::getTypeSize(VET_FLOAT2);

		mIndices[1] = new IndexData();
		mIndices[1]->indexStart = 0;
		mIndices[1]->indexCount = mSceneMgr->getIndexBufferManager()->getIndexBufferSize(INDEX_BUFFER_ATMO,LOD_MID);
		
		// LOD MIN
		mVertices[2] = new VertexData();
		mVertices[2]->vertexStart = 0;
		mVertices[2]->vertexCount = (rowlength2 * rowlength2);

		decl = mVertices[2]->vertexDeclaration;

		offset = 0;
		decl->addElement(0,0,VET_FLOAT3,VES_POSITION);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT3, VES_NORMAL);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT2, VES_TEXTURE_COORDINATES);
		offset += VertexElement::getTypeSize(VET_FLOAT2);

		mIndices[2] = new IndexData();
		mIndices[2]->indexStart = 0;
		mIndices[2]->indexCount = mSceneMgr->getIndexBufferManager()->getIndexBufferSize(INDEX_BUFFER_ATMO,LOD_MIN);

		mIsInit = true;
	}

	void PlanetaryAtmosphereRenderable::load()
	{
		mBounds.setNull();

		for(unsigned int i = 0; i < 3; i++)
		{
			VertexDeclaration *decl = mVertices[i]->vertexDeclaration;
			VertexBufferBinding *bind = mVertices[i]->vertexBufferBinding;

			// Setup vertices
			HardwareVertexBufferSharedPtr vbuf = 
					HardwareBufferManager::getSingleton().createVertexBuffer(
					decl->getVertexSize(0),
					mVertices[i]->vertexCount,
					HardwareBuffer::HBU_STATIC_WRITE_ONLY);

			bind->setBinding(0,vbuf);

			const VertexElement* poselem = decl->findElementBySemantic(VES_POSITION);

			unsigned int vertexSize = vbuf->getVertexSize();

			uchar *pBuffer = (uchar*)vbuf->lock(HardwareBuffer::HBL_DISCARD);

			switch(mZone)
			{
				case ZONE_UP:
					loadUp(poselem,pBuffer,vertexSize,i);
					break;
				case ZONE_DOWN:
					loadDown(poselem,pBuffer,vertexSize,i);
					break;
				case ZONE_FRONT:
					loadFront(poselem,pBuffer,vertexSize,i);
					break;
				case ZONE_BACK:
					loadBack(poselem,pBuffer,vertexSize,i);
					break;
				case ZONE_LEFT:
					loadLeft(poselem,pBuffer,vertexSize,i);
					break;
				case ZONE_RIGHT:
					loadRight(poselem,pBuffer,vertexSize,i);
					break;
			}

			vbuf->unlock();

			// Setup indices
			mIndices[i]->indexBuffer = mSceneMgr->getIndexBufferManager()->getIndexBuffer(INDEX_BUFFER_ATMO,i);
		}


		float x = (mBounds.getMaximum().x - mBounds.getMinimum().x);
		float y = (mBounds.getMaximum().y - mBounds.getMinimum().y);
		float z = (mBounds.getMaximum().y - mBounds.getMinimum().y);

		mCenter = Vector3(mBounds.getMinimum().x + (x/2.0f),mBounds.getMinimum().y + (y/2.0f),mBounds.getMinimum().z + (z/2.0f));

		mBoundingRadius = Math::Sqrt(Math::Sqr(x) + Math::Sqr(y) + Math::Sqr(z)) / 2.0f;

		mIsLoaded = true;
	}


	void PlanetaryAtmosphereRenderable::loadUp(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize, unsigned int LOD)
	{
		unsigned int rowlength;

		switch(LOD)
		{
		case LOD_MAX:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;

		case LOD_MID:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;

		case LOD_MIN:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		float *pPos;

		for(unsigned int i = 0; i < rowlength; i++)
		{
			for(unsigned int j = 0; j < rowlength; j++)
			{
				float x = (2.0f * static_cast<float>(j)/static_cast<float>(rowlength - 1)) - 1.0f;

				float y = 1.0f;

				float z = (2.0f * static_cast<float>(i)/static_cast<float>(rowlength - 1)) - 1.0f;

				Vector3 pos = cubeToSphereCoords(x,y,z) * mRadius;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				// Normals
				Vector3 norm = pos.normalisedCopy();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				Vector2 tex(((x + 1.0f) / 2), ((z + 1.0f) / 2));
				*pPos++ = tex.x;
				*pPos++ = tex.y;

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryAtmosphereRenderable::loadDown(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize, unsigned int LOD)
	{
		unsigned int rowlength;

		switch(LOD)
		{
		case LOD_MAX:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;

		case LOD_MID:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;

		case LOD_MIN:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		float *pPos;

		for(int i = (int)rowlength-1; i >= 0; i--)
		{
			for(unsigned int j = 0; j < rowlength; j++)
			{
				float x = (2.0f * static_cast<float>(j)/static_cast<float>(rowlength - 1)) - 1.0f;

				float y = -1.0f;

				float z = (2.0f * static_cast<float>(i)/static_cast<float>(rowlength - 1))- 1.0f;

				Vector3 pos = cubeToSphereCoords(x,y,z) * mRadius;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				// Normals
				Vector3 norm = pos.normalisedCopy();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((x + 1.0f) / 2);
				*pPos++ = ((z + 1.0f) / 2);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryAtmosphereRenderable::loadFront(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize, unsigned int LOD)
	{
		unsigned int rowlength;

		switch(LOD)
		{
		case LOD_MAX:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;

		case LOD_MID:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;

		case LOD_MIN:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		float *pPos;

		for(int i = (int)rowlength - 1; i >= 0; i--)
		{
			for(unsigned int j = 0; j < rowlength; j++)
			{
				float x = (2.0f * static_cast<float>(j)/static_cast<float>(rowlength - 1)) - 1.0f;

				float y = (2.0f * static_cast<float>(i)/static_cast<float>(rowlength - 1)) - 1.0f;

				float z = 1.0f;

				Vector3 pos = cubeToSphereCoords(x,y,z) * mRadius;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				// Normals
				Vector3 norm = pos.normalisedCopy();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((x + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryAtmosphereRenderable::loadBack(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize, unsigned int LOD)
	{
		unsigned int rowlength;

		switch(LOD)
		{
		case LOD_MAX:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;

		case LOD_MID:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;

		case LOD_MIN:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		float *pPos;

		for(unsigned int i = 0; i < rowlength; i++)
		{
			for(unsigned int j = 0; j < rowlength; j++)
			{
				float x = (2.0f * static_cast<float>(j)/static_cast<float>(rowlength - 1)) - 1.0f;

				float y = (2.0f * static_cast<float>(i)/static_cast<float>(rowlength - 1)) - 1.0f;

				float z = -1.0f;

				Vector3 pos = cubeToSphereCoords(x,y,z) * mRadius;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				// Normals
				Vector3 norm = pos.normalisedCopy();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((x + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryAtmosphereRenderable::loadLeft(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize, unsigned int LOD)
	{
		unsigned int rowlength;

		switch(LOD)
		{
		case LOD_MAX:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;

		case LOD_MID:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;

		case LOD_MIN:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		float *pPos;

		for(int i = (int)rowlength - 1; i >= 0; i--)
		{
			for(unsigned int j = 0; j < rowlength; j++)
			{
				float x = -1.0f;

				float y = (2.0f * static_cast<float>(i)/static_cast<float>(rowlength - 1)) - 1.0f;

				float z = (2.0f * static_cast<float>(j)/static_cast<float>(rowlength - 1)) - 1.0f;

				Vector3 pos = cubeToSphereCoords(x,y,z) * mRadius;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				// Normals
				Vector3 norm = pos.normalisedCopy();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((z + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryAtmosphereRenderable::loadRight(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize, unsigned int LOD)
	{
		unsigned int rowlength;

		switch(LOD)
		{
		case LOD_MAX:
			rowlength = mOptions->mAtmoSizeLOD0;
			break;

		case LOD_MID:
			rowlength = mOptions->mAtmoSizeLOD1;
			break;

		case LOD_MIN:
			rowlength = mOptions->mAtmoSizeLOD2;
			break;
		}

		float *pPos;

		for(unsigned int i = 0; i < rowlength; i++)
		{
			for(unsigned int j = 0; j < rowlength; j++)
			{
				float x = 1.0f;

				float y = (2.0f * static_cast<float>(i)/static_cast<float>(rowlength - 1)) - 1.0f;

				float z = (2.0f * static_cast<float>(j)/static_cast<float>(rowlength - 1)) - 1.0f;;

				Vector3 pos = cubeToSphereCoords(x,y,z) * mRadius;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				// Normals
				Vector3 norm = pos.normalisedCopy();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((z + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryAtmosphereRenderable::unload()
	{
		mIsLoaded = false;
	}

	void PlanetaryAtmosphereRenderable::uninit()
	{
		mIsInit = false;
	}

	void PlanetaryAtmosphereRenderable::_notifyCurrentCamera(Camera* cam)
	{
		if(!mIsLoaded || !mIsInit)
		{
			mVisible = false;
			return;
		}

		mLodIndex = 2;

		Sphere camsphere,camsphere2; 
		camsphere.setCenter(cam->getRealPosition());
		camsphere.setRadius(mOptions->mAtmoLOD1Distance);

		if(getBoundingBox().intersects(camsphere))
		{
			mLodIndex = 1;

			camsphere2.setCenter(cam->getRealPosition());
			camsphere2.setRadius(mOptions->mAtmoLOD0Distance);

			if(getBoundingBox().intersects(camsphere2))
				mLodIndex = 0;
		}

	}

	void PlanetaryAtmosphereRenderable::_updateRenderQueue(RenderQueue* queue)
	{
		queue->addRenderable(this);
	}

	void PlanetaryAtmosphereRenderable::_notifyAttached(Node* parent, bool isTagPoint)
	{
		MovableObject::_notifyAttached(parent, isTagPoint);
	}

	void PlanetaryAtmosphereRenderable::getRenderOperation(RenderOperation& op)
	{
		op.useIndexes = true;
		op.operationType = RenderOperation::OT_TRIANGLE_LIST;
		op.vertexData = mVertices[mLodIndex];
		op.indexData = mIndices[mLodIndex];
	}

	void PlanetaryAtmosphereRenderable::getWorldTransforms(Matrix4 *xform) const
	{
		*xform = mParentNode->_getFullTransform();
	}

	const Quaternion &PlanetaryAtmosphereRenderable::getWorldOrientation() const
	{
		return mParentNode->_getDerivedOrientation();
	}

	const Vector3 &PlanetaryAtmosphereRenderable::getWorldPosition() const
	{
		return mCenter;
	}
	
	Real PlanetaryAtmosphereRenderable::getSquaredViewDepth(const Camera *cam) const
	{
		return (PlanetaryAtmosphereRenderable::getWorldPosition() - cam->getDerivedPosition()).squaredLength();
	}

	const LightList &PlanetaryAtmosphereRenderable::getLights() const
	{
		if (mLightListDirty)
		{
			getParentSceneNode()->getCreator()->_populateLightList(mCenter, this->getBoundingRadius(), mLightList);
			mLightListDirty = false;
		}
		return mLightList;
	}

}
